#include <unistd.h>
#include <assert.h>

#include <gr_graph_fun.h>
#include "fuzzycluster.h"
#include "utility.h"
#include <hash_table.h>

void
version(void)
{
	printf("%s\n",PACKAGE_STRING);
	printf("Send patches and bug reports to %s\n",PACKAGE_BUGREPORT);
}

void
help(void)
{
	version();
	printf("\n");
	printf("Usage: %s [--version] [--help] [--max-cluster-size]\n",PACKAGE_NAME);
	printf("\t[--min-cluster-size] [--page-rank-delete] xgmml_graph\n");
	printf("\tWhere 'xgmml_graph' is an XGMML graph file\n");
	printf("\n");
	printf("  -v,--version                    Print version information and quit\n");
	printf("  -h,--help                       Print this help information and quit\n");
	printf("  -a,--max-cluster-size=<SIZE>    Set the max cluster size (default: 20)\n");
	printf("  -i,--min-cluster-size=<SIZE>    Set the min cluster size (default: 5)\n");
	printf("  -p,--page-rank-ignore=<NUM>     Set the number of high page rank nodes to\n");
	printf("                                   ignore at each reduction (default: 10)\n");
	printf("  -t,--add-iter=<NUM>             Maximum iterations of adding removed\n");
	printf("                                   vertices (default: 99999)\n");
	printf("  -d,--debug=<LEVEL>              Debug level (default: 0) 0 being no debug\n");
	printf("                                  information, 3 being lots\n");
	printf("  -r,--prefix=<STRING>            Discovered cluster prefix (default: cluster-)\n");
}


/* This function is a copy of compute_connected_components, but
  I wanted to ignore selected nodes in the graph (i.e. pretend
  they are deleted) */
int
compute_connected_components_ignore_selected(GraphFrame *gf)
{
	int nc = 1;
	LNode_type *ptr;
	reset_mark_pick_vertices(gf);
	
	/* we ignore everything in the list of ignore vertices */
	for(ptr = gf->list_sel_vertex->back; ptr != gf->list_sel_vertex; ptr = ptr->back)
		((PNODE)(ptr->info))->mark = ((PNODE)(ptr->info))->pick = True;
	
	if(gf->graph_dir == DIRECTED) {
		for(ptr= gf->list_vertices->back; ptr != gf->list_vertices ; ptr = ptr->back) {
			PNODE v = (PNODE)ptr->info;
			if(!v->pick)
				dfs_connected_component_directed(v, nc++);
		}
  	}
	else {
		for(ptr= gf->list_vertices->back; ptr != gf->list_vertices ; ptr = ptr->back) {
			PNODE v = (PNODE)ptr->info;
			if(!v->pick)
				dfs_connected_component_undirected(v, nc++);
		}
	}
	printf("%d connected components computed \n",nc-1);
	set_num_components(gf,nc-1);
	return nc-1;
}

int
insertSelectedNodes_iter(GraphFrame *graph)
{
	LNode_type *ptr, *eptr;
	PNODE source,target;
	PEDGE edge;
	unsigned int added = 0;
	

	/* For each node */
	for(ptr = graph->list_vertices->back; ptr != graph->list_vertices; ptr=ptr->back) {
		PNODE node = (PNODE)ptr->info;

		/* For each node's in edge */
		for(eptr = node->ledges_in->back; eptr != node->ledges_in; eptr=eptr->back) {
			edge = (PEDGE)eptr->info;
			source = edge->from;
			/* If the source node is selected but not in the cluster, add it */
			if(source->select && source->context != node->context) {
				source->context = node->context;
				Insert_linked_list(graph->list_vertices,create_new_node_llist((char *)source));
				++added;
				++(graph->count_vertex);
			}
		}
				
		/* If the graph is directed */
		if(graph->graph_dir == DIRECTED) {
			/* For each node's out edges */
			for(eptr = node->ledges_out->back; eptr != node->ledges_out; eptr=eptr->back) {
				edge = (PEDGE)eptr->info;
				target = edge->to;
				
				/* If the target node is selected but not in the cluster, add it */
				if(target->select && target->context != node->context) {
					target->context = node->context;
					Insert_linked_list(graph->list_vertices,create_new_node_llist((char *)target));
					++added;
					++(graph->count_vertex);
				}
			}
		}
	}

	return added;
}

void
insertSelectedNodes(GraphFrame *graph, unsigned int iterations)
{
	int added =1;
	double density, density1;
	
	density1 = (double)edge_count(graph) / (double)graph->count_vertex;

	do {
		
		density = density1;
		added = insertSelectedNodes_iter(graph);
		density1 = (double)edge_count(graph) / (double)graph->count_vertex;
		
	} while((density1 > density) && added);

}

LNode_type *
list_component_i(GraphFrame *graph, unsigned int component_number)
{
	LNode_type *selected_save;
	LNode_type *component;
	LNode_type *ptr;

	selected_save = graph->list_sel_vertex;
	graph->list_sel_vertex = init_linked_list();

	select_i_connected_component_vertices(graph,component_number);
	
	component = graph->list_sel_vertex;
	graph->list_sel_vertex = selected_save;
	
	/* Deselect all vertices */
	for(ptr=component->back;ptr!=component;ptr=ptr->back)
		((PNODE)(ptr->info))->select = False;	
	
	return component;
}

int
cluster_guess(GraphFrame *gf, LNode_type ***clusters)
{
	int nc = 1;
	LNode_type *ptr;
	
	LNode_type **components, *component;
	int i;

	reset_mark_pick_vertices(gf);

	/* we pretend selected vertices don't exist, and cannot be traversed */
	for(ptr = gf->list_sel_vertex->back; ptr != gf->list_sel_vertex; ptr = ptr->back)
		((PNODE)(ptr->info))->mark = ((PNODE)(ptr->info))->pick = True;
	
	if(gf->graph_dir == DIRECTED) {
		for(ptr= gf->list_vertices->back; ptr != gf->list_vertices ; ptr = ptr->back) {
			PNODE v = (PNODE)ptr->info;
			if(!v->pick)
				dfs_connected_component_directed(v, nc++);
		}
  	}
	else {
		for(ptr= gf->list_vertices->back; ptr != gf->list_vertices ; ptr = ptr->back) {
			PNODE v = (PNODE)ptr->info;
			if(!v->pick)
				dfs_connected_component_undirected(v, nc++);
		}
	}
	printf("%d connected components computed \n",nc-1);
	set_num_components(gf,nc-1); /* this line is meaningless */
	
	if(!(components = (LNode_type **)malloc(sizeof(LNode_type *)*(nc-1))))
		abort();
	
	for(i=0;i<nc-1;i++) {
		component = list_component_i(gf,i+1);
		components[i] = component;

		/* foreach selected node, if it's connected to the current component, add it */
		/*
		for(ptr = gf->list_sel_vertex->back; ptr != gf->list_sel_vertex; ptr = ptr->back) {
			PNODE node = (PNODE)ptr->info;
			LNode_type *eptr;
			
			node->context = NULL;
			
			for(eptr = node->ledges_in->back; eptr != node->ledges_in; eptr = eptr->back) {
				PEDGE edge = (PEDGE)eptr->info;
				if(((int)((PNODE)(edge->from))->context == (i+1)) && ((PNODE)edge->from)->select == False ) {
					Insert_linked_list(component,create_new_node_llist((char *)node));
					break;
				}
			}

			if(gf->graph_dir == DIRECTED) {
				LNode_type *eptr;
				for(eptr = node->ledges_out->back; eptr != node->ledges_out; eptr = eptr->back) {
					PEDGE edge = (PEDGE)eptr->info;
					if(((int)((PNODE)(edge->to))->context == (i+1)) && ((PNODE)edge->to)->select == False) {
						Insert_linked_list(component,create_new_node_llist((char *)node));
						break;
					}
				}
			}
		}
		*/
	}
	
	/*deselect_all_nodes(gf);*/
	
	*clusters = components;
	return nc-1;
}

GraphFrame *
load_graph(char *filename)
{
	FILE *xgmml;
	long len = 0,tlen = 0;
	char *data = NULL;
	char tmp[MAX_READ_LENGTH];
	GraphFrame *graph;

	assert(filename != NULL);

	graph = create_current_graph();
		
	if(!(xgmml = fopen(filename,"r"))) {
		perror(filename);
		exit(EXIT_FAILURE);
	}

	while((tlen = fread(tmp,sizeof(char),MAX_READ_LENGTH,xgmml))>0) {
		if((data = (char *)realloc(data,sizeof(char)*(len+tlen+1)))<0) {
			perror("realloc");
			abort();
		}

		memcpy(&data[len],tmp,tlen);
		len += tlen;
	}
	parse_xml_text_graph(graph,data,len);
	free(data);
	
	return graph;	
}

Hashtable_type *
trimmed_edges(GraphFrame *graph)
{
	LNode_type *ptr;
	LNode_type *eptr;
	PNODE node;
	PEDGE edge;
	Hashtable_type *HEdges;
	
	if(!(HEdges = (Hashtable_type *)calloc(1,sizeof(Hashtable_type)))) {
		perror("calloc");
		abort();
	}
	
	/* dont need to with calloc above ... HEdges = init_hash_table(...) */

	for(ptr=graph->list_vertices->back;ptr != graph->list_vertices; ptr=ptr->back) {
		node=(PNODE)ptr->info;
		for(eptr=node->ledges_in->back;eptr!=node->ledges_in;eptr=eptr->back) {
			edge = (PEDGE)eptr->info;
			if(edge->from->context == node->context)
				insert_hash_info(*HEdges, edge->label, (char *)edge);
		}
		
		if(graph->graph_dir == DIRECTED) {
			node=(PNODE)eptr->info;
			for(eptr=node->ledges_out->back;eptr!=node->ledges_out;eptr=eptr->back) {
				edge = (PEDGE)ptr->info;
				if(edge->to->context == node->context)
					insert_hash_info(*HEdges, edge->label, (char *)edge);
			}			
		}
		
	}

	return HEdges;
}

void
save_graph_context(GraphFrame *graph, char *filename, void *context)
{
	FILE *output;
	char *xgmml;
	long length;
	long written;

	Hashtable_type oldedges;
	Hashtable_type *newedges;
	unsigned int i;
	
	LNode_type *ptr;
	
	assert(graph != NULL);
	assert(filename != NULL);
	
	/*
	newedges = trimmed_edges(graph);
	for(i=0;i<HASHSIZE;i++) {
		oldedges[i] = (graph->Hedge)[i];
		graph->Hedge[i] = (*newedges)[i];
	}
	*/
			
	for(ptr=graph->list_vertices->back; ptr!= graph->list_vertices;ptr=ptr->back)
		((PNODE)ptr->info)->context = (void *)-1;

	if(!(xgmml = (char *)get_xml_text_graph(graph,&length,GR_STANDARD_FORMAT,(void *)-1))) {
		fprintf(stderr,"Could not generate XGMML. Perhaps the graph is screwy.\n");
		return;
	}
	
	for(ptr=graph->list_vertices->back; ptr!= graph->list_vertices;ptr=ptr->back)
		((PNODE)ptr->info)->context = (void *)0;	
	
	if(!(output = fopen(filename,"w"))) {
		free(xgmml);
		perror(filename);
		return;
	}
	
	while(length && (written = fwrite(xgmml,sizeof(char),length,output)))
		length -= written;

	free(xgmml);
	
	/*	
	for(i=0;i<HASHSIZE;i++)
		graph->Hedge[i] = oldedges[i];
	
	Delete_hash_table(*newedges);
	free(newedges);
	Delete_hash_table(oldedges);
	*/
}

void
print_graph(GraphFrame *graph)
{
	char *xgmml;
	long length;
	long written;
	
	assert(graph != NULL);
	
	if(!(xgmml = (char *)get_xml_text_graph(graph,&length,GR_STANDARD_FORMAT,(void *)-1))) {
		fprintf(stderr,"Could not generate XGMML. Perhaps the graph is screwy.\n");
		return;
	}
	
	while(length && (written = fwrite(xgmml,sizeof(char),length,stdout)))
		length -= written;

	free(xgmml);
}

unsigned long
edge_count(GraphFrame *graph)
{
	unsigned long edge_count;
	
	LNode_type *edges, *ptr;
	
	edges = get_list_elements(graph->Hedge);
	
	for(ptr=edges->back;ptr!=edges;ptr=ptr->back)
		++edge_count;
	
	return edge_count;
	
}
